package com.bys.admin.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.PageResult;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.engine.velocity.VelocityEngine;
import cn.hutool.system.SystemUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bys.admin.system.entity.SysCodegenColumn;
import com.bys.admin.system.entity.SysCodegenTable;
import com.bys.config.BaseEntity;
import com.bys.eunm.CodegenFrontTypeEnum;
import com.bys.eunm.CodegenSceneEnum;
import com.bys.eunm.CodegenTemplateTypeEnum;
import com.bys.properties.CodeGenTableProperties;
import com.bys.response.R;
import com.bys.util.BeanUtils;
import com.bys.util.CollectionUtils;
import com.bys.util.ServiceExceptionUtil;
import com.bys.vo.PageVo;
import com.github.yulichang.base.MPJBaseMapper;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import jakarta.annotation.PostConstruct;
import lombok.Setter;
import org.springframework.stereotype.Component;

import jakarta.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static cn.hutool.core.map.MapUtil.getStr;
import static cn.hutool.core.text.CharSequenceUtil.*;
import static cn.hutool.core.text.CharSequenceUtil.toSymbolCase;

/**
 * 代码生成的引擎，用于具体生成代码
 */
@Component
public class CodegenEngine {
    private final TemplateEngine templateEngine;
    @Setter // 允许设置的原因，是因为单测需要手动改变
    private Boolean jakartaEnable;
    @Resource
    private CodeGenTableProperties codeGenTableProperties;
    /**
     * 全局通用变量映射
     */
    private final Map<String, Object> globalBindingMap = new HashMap<>();
    public CodegenEngine() {
        // 初始化 TemplateEngine 属性
        TemplateConfig config = new TemplateConfig();
        config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH);
        this.templateEngine = new VelocityEngine(config);
        // 设置 javaxEnable，按照是否使用 JDK17 来判断
        this.jakartaEnable = SystemUtil.getJavaInfo().isJavaVersionAtLeast(1700); // 17.00 * 100
    }
    /**
     * 后端的配置模版
     *
     * key1：UI 模版的类型 {@link CodegenFrontTypeEnum#getType()}
     * key2：模板在 resources 的地址
     * value：生成的路径
     */
    private static final Table<Integer, String, String> FRONT_TEMPLATES = ImmutableTable.<Integer, String, String>builder()
            // Vue3 标准模版
            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/detail.vue"),
                    vue3FilePath("views/${table.moduleName}/${table.businessName}/detail.vue"))
            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"),
                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
            .build();

    /**
     * 后端的模板配置
     *
     * key：模板在 resources 的地址
     * value：生成的路径
     */
    private static final Map<String, String> SERVER_TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序
            // Java module-biz Main
            .put(javaTemplatePath("controller/vo/pageReqVO"), javaModuleImplVOFilePath("PageReqVO"))
            .put(javaTemplatePath("controller/vo/saveReqVO"), javaModuleImplVOFilePath("SaveReqVO"))
            .put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
            .put(javaTemplatePath("entity/entity"),
                    javaModuleImplMainFilePath("entity/${table.className}"))
            .put(javaTemplatePath("entity/entity_sub"), // 特殊：主子表专属逻辑
                    javaModuleImplMainFilePath("entity/${subTable.className}"))
            .put(javaTemplatePath("mapper/mapper"),
                    javaModuleImplMainFilePath("mapper/${table.className}Mapper"))
            .put(javaTemplatePath("mapper_sub"), // 特殊：主子表专属逻辑
                    javaModuleImplMainFilePath("mapper_sub/${subTable.className}Mapper"))
            .put(javaTemplatePath("mapper/mapper.xml"), "${sceneEnum.basePackage}/mapper/${table.className}Mapper.xml")
            .put(javaTemplatePath("service/serviceImpl"),
                    javaModuleImplMainFilePath("service/impl/${table.className}ServiceImpl"))
            .put(javaTemplatePath("service/service"),
                    javaModuleImplMainFilePath("service/${table.className}Service"))
            // SQL
            .put("codegenTable/sql/sql.vm", "sql/sql.sql")
            .build();


    private static String javaTemplatePath(String path) {
        return "codegentable/java/" + path + ".vm";
    }

    private static String javaModuleImplVOFilePath(String path) {
        return javaModuleFilePath("controller/" +
                "vo/${sceneEnum.prefixClass}${table.className}" + path);
    }
    private static String javaModuleImplControllerFilePath() {
        return javaModuleFilePath("controller/" +
                "${sceneEnum.prefixClass}${table.className}Controller");
    }

    private static String javaModuleImplMainFilePath(String path) {
        return javaModuleFilePath(path);
    }

    private static String javaModuleApiMainFilePath(String path) {
        return javaModuleFilePath(path);
    }

    private static String javaModuleImplTestFilePath(String path) {
        return javaModuleFilePath(path);
    }

    private static String javaModuleFilePath(String path) {
        return "${sceneEnum.basePackage}/${table.moduleName}" + path + ".java";
    }

    private static String vueTemplatePath(String path) {
        return "codegenTable/vue/" + path + ".vm";
    }

    private static String vueFilePath(String path) {
        return "yudao-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录
                "src/" + path;
    }

    private static String vue3TemplatePath(String path) {
        return "codegentable/vue3/" + path + ".vm";
    }

    private static String vue3FilePath(String path) {
        return "vue3/" +  path;
    }

    private static String vue3VbenTemplatePath(String path) {
        return "codegen/vue3_vben/" + path + ".vm";
    }

    private static boolean isSubTemplate(String path) {
        return path.contains("_sub");
    }

    private static boolean isPageReqVOTemplate(String path) {
        return path.contains("pageReqVO");
    }

    private static boolean isListReqVOTemplate(String path) {
        return path.contains("listReqVO");
    }

    /**
     * 生成代码
     * @param codegenTable  表定义
     * @param columns  table 的字段定义数组
     * @param subTables  子表数组，当且仅当主子表时使用
     * @param subColumnsList  subTables 的字段定义数组
     *@return 生成的代码，key 是路径，value 是对应代码
     */
    public Map<String, String> execute(SysCodegenTable codegenTable, List<SysCodegenColumn> columns, List<SysCodegenTable> subTables, List<List<SysCodegenColumn>> subColumnsList) {
        // 1.1 初始化 bindMap 上下文
        Map<String, Object> bindingMap = initBindingMap(codegenTable, columns, subTables, subColumnsList);
        // 1.2 获得模版
        Map<String, String> templates = getTemplates(codegenTable.getFrontType());
        // 2. 执行生成
        Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
        templates.forEach((vmPath, filePath) -> {
            // 2.1 特殊：主子表专属逻辑
            if (isSubTemplate(vmPath)) {
//                generateSubCode(codegenTable, subTables, result, vmPath, filePath, bindingMap);
                return;
                // 2.2 特殊：树表专属逻辑
            } else if (isPageReqVOTemplate(vmPath)) {
                // 减少多余的类生成，例如说 PageVO.java 类
                if (CodegenTemplateTypeEnum.isTree(codegenTable.getTemplateType())) {
                    return;
                }
            } else if (isListReqVOTemplate(vmPath)) {
                // 减少多余的类生成，例如说 ListVO.java 类
                if (!CodegenTemplateTypeEnum.isTree(codegenTable.getTemplateType())) {
                    return;
                }
            }
            // 2.3 默认生成
            generateCode(result, vmPath, filePath, bindingMap);
        });
        return result;
    }

    private void generateSubCode(SysCodegenTable codegenTable, List<SysCodegenTable> subTables, Map<String, String> result, String vmPath, String filePath, Map<String, Object> bindingMap) {

    }

    private void generateCode(Map<String, String> result, String vmPath, String filePath, Map<String, Object> bindingMap) {
        filePath = formatFilePath(filePath, bindingMap);
        String content = templateEngine.getTemplate(vmPath).render(bindingMap);
        // 格式化代码
        content = prettyCode(content);
        result.put(filePath, content);
    }

    private String prettyCode(String content) {
        // Vue 界面：去除字段后面多余的 , 逗号，解决前端的 Pretty 代码格式检查的报错
        content = content.replaceAll(",\n}", "\n}").replaceAll(",\n  }", "\n  }");
        // Vue 界面：去除多的 dateFormatter，只有一个的情况下，说明没使用到
        if (StrUtil.count(content, "dateFormatter") == 1) {
            content = removeLineContains(content, "dateFormatter");
        }
        // Vue2 界面：修正 $refs
        if (StrUtil.count(content, "this.refs") >= 1) {
            content = content.replace("this.refs", "this.$refs");
        }
        // Vue 界面：去除多的 dict 相关，只有一个的情况下，说明没使用到
        if (StrUtil.count(content, "getIntDictOptions") == 1) {
            content = content.replace("getIntDictOptions, ", "");
        }
        if (StrUtil.count(content, "getStrDictOptions") == 1) {
            content = content.replace("getStrDictOptions, ", "");
        }
        if (StrUtil.count(content, "getBoolDictOptions") == 1) {
            content = content.replace("getBoolDictOptions, ", "");
        }
        if (StrUtil.count(content, "DICT_TYPE.") == 0) {
            content = removeLineContains(content, "DICT_TYPE");
        }
        return content;
    }

    private String removeLineContains(String content, String sequence) {
        if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
            return content;
        }
        return Arrays.stream(content.split("\n"))
                .filter(line -> !line.contains(sequence))
                .collect(Collectors.joining("\n"));
    }

    private String formatFilePath(String filePath, Map<String, Object> bindingMap) {
        filePath = StrUtil.replace(filePath, "${basePackage}",
                getStr(bindingMap, "basePackage").replaceAll("\\.", "/"));
        filePath = StrUtil.replace(filePath, "${classNameVar}",
                getStr(bindingMap, "classNameVar"));
        filePath = StrUtil.replace(filePath, "${simpleClassName}",
                getStr(bindingMap, "simpleClassName"));
        // sceneEnum 包含的字段
        CodegenSceneEnum sceneEnum = (CodegenSceneEnum) bindingMap.get("sceneEnum");
        filePath = StrUtil.replace(filePath, "${sceneEnum.prefixClass}", sceneEnum.getPrefixClass());
        filePath = StrUtil.replace(filePath, "${sceneEnum.basePackage}", sceneEnum.getBasePackage());
        // table 包含的字段
        SysCodegenTable table = (SysCodegenTable) bindingMap.get("table");
        filePath = StrUtil.replace(filePath, "${table.moduleName}", table.getModuleName());
        filePath = StrUtil.replace(filePath, "${table.businessName}", table.getBusinessName());
        filePath = StrUtil.replace(filePath, "${table.className}", table.getClassName());
        // 特殊：主子表专属逻辑
        Integer subIndex = (Integer) bindingMap.get("subIndex");
        if (subIndex != null) {
            SysCodegenTable subTable = ((List<SysCodegenTable>) bindingMap.get("subTables")).get(subIndex);
            filePath = StrUtil.replace(filePath, "${subTable.moduleName}", subTable.getModuleName());
            filePath = StrUtil.replace(filePath, "${subTable.businessName}", subTable.getBusinessName());
            filePath = StrUtil.replace(filePath, "${subTable.className}", subTable.getClassName());
            filePath = StrUtil.replace(filePath, "${subSimpleClassName}",
                    ((List<String>) bindingMap.get("subSimpleClassNames")).get(subIndex));
        }
        return filePath;

    }

    private Map<String, String> getTemplates(Integer frontType) {
        Map<String, String> templates = new LinkedHashMap<>();
        templates.putAll(SERVER_TEMPLATES);
        templates.putAll(FRONT_TEMPLATES.row(frontType));
        return templates;
    }

    private Map<String, Object> initBindingMap(SysCodegenTable codegenTable, List<SysCodegenColumn> columns, List<SysCodegenTable> subTables, List<List<SysCodegenColumn>> subColumnsList) {  // 创建 bindingMap
        Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);
        bindingMap.put("table", codegenTable);
        bindingMap.put("columns", columns);
        bindingMap.put("primaryColumn", CollectionUtils.findFirst(columns, SysCodegenColumn::getPrimaryKey)); // 主键字段
        bindingMap.put("sceneEnum", CodegenSceneEnum.valueOf(codegenTable.getScene()));
        // className 相关
        // 去掉指定前缀，将 TestDictType 转换成 DictType. 因为在 create 等方法后，不需要带上 Test 前缀
        String simpleClassName = equalsAnyIgnoreCase(codegenTable.getClassName(), codegenTable.getModuleName()) ? codegenTable.getClassName()
                : removePrefix(codegenTable.getClassName(), upperFirst(codegenTable.getModuleName()));
        bindingMap.put("simpleClassName", simpleClassName);
        bindingMap.put("simpleClassName_underlineCase", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type
        bindingMap.put("classNameVar", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType，用于变量
        // 将 DictType 转换成 dict-type
        String simpleClassNameStrikeCase = toSymbolCase(simpleClassName, '-');
        bindingMap.put("simpleClassName_strikeCase", simpleClassNameStrikeCase);
        // permission 前缀
        bindingMap.put("permissionPrefix", codegenTable.getModuleName() + ":" + simpleClassNameStrikeCase);
        // 特殊：树表专属逻辑
        if (CodegenTemplateTypeEnum.isTree(codegenTable.getTemplateType())) {
            SysCodegenColumn treeParentColumn = CollUtil.findOne(columns,
                    column -> Objects.equals(column.getId(), codegenTable.getTreeParentColumnId()));
            bindingMap.put("treeParentColumn", treeParentColumn);
            bindingMap.put("treeParentColumn_javaField_underlineCase", toUnderlineCase(treeParentColumn.getJavaField()));
            SysCodegenColumn treeNameColumn = CollUtil.findOne(columns,
                    column -> Objects.equals(column.getId(), codegenTable.getTreeNameColumnId()));
            bindingMap.put("treeNameColumn", treeNameColumn);
            bindingMap.put("treeNameColumn_javaField_underlineCase", toUnderlineCase(treeNameColumn.getJavaField()));
        }
        // 特殊：主子表专属逻辑
        if (CollUtil.isNotEmpty(subTables)) {
            // 创建 bindingMap
            bindingMap.put("subTables", subTables);
            bindingMap.put("subColumnsList", subColumnsList);
            List<SysCodegenColumn> subPrimaryColumns = new ArrayList<>();
            List<SysCodegenColumn> subJoinColumns = new ArrayList<>();
            List<String> subJoinColumnStrikeCases = new ArrayList<>();
            List<String> subSimpleClassNames = new ArrayList<>();
            List<String> subClassNameVars = new ArrayList<>();
            List<String> simpleClassNameUnderlineCases = new ArrayList<>();
            List<String> subSimpleClassNameStrikeCases = new ArrayList<>();
            for (int i = 0; i < subTables.size(); i++) {
                SysCodegenTable subTable = subTables.get(i);
                List<SysCodegenColumn> subColumns = subColumnsList.get(i);
                subPrimaryColumns.add(CollectionUtils.findFirst(subColumns, SysCodegenColumn::getPrimaryKey)); //
                SysCodegenColumn subColumn = CollectionUtils.findFirst(subColumns, // 关联的字段
                        column -> Objects.equals(column.getId(), subTable.getSubJoinColumnId()));
                subJoinColumns.add(subColumn);
                subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type
                // className 相关
                String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
                subSimpleClassNames.add(subSimpleClassName);
                simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type
                subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType，用于变量
                subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type
            }
            bindingMap.put("subPrimaryColumns", subPrimaryColumns);
            bindingMap.put("subJoinColumns", subJoinColumns);
            bindingMap.put("subJoinColumn_strikeCases", subJoinColumnStrikeCases);
            bindingMap.put("subSimpleClassNames", subSimpleClassNames);
            bindingMap.put("simpleClassNameUnderlineCases", simpleClassNameUnderlineCases);
            bindingMap.put("subClassNameVars", subClassNameVars);
            bindingMap.put("subSimpleClassName_strikeCases", subSimpleClassNameStrikeCases);
        }
        return bindingMap;
    }

    @PostConstruct
    void initGlobalBindingMap(){
        // 全局配置
        globalBindingMap.put("basePackage", codeGenTableProperties.getBasePackage());
        globalBindingMap.put("baseFrameworkPackage", codeGenTableProperties.getBasePackage() + '.' + "framework"); // 用于后续获取测试类的 package 地址
        globalBindingMap.put("jakartaPackage", jakartaEnable ? "jakarta" : "javax");
        globalBindingMap.put("RClassName", R.class.getName());
        // VO 类，独有字段
        globalBindingMap.put("PageVoClassName", PageVo.class.getName());
        // DO 类，独有字段
        globalBindingMap.put("BaseEnityClassName", BaseEntity.class.getName());
        globalBindingMap.put("baseDOFields", CodegenBuilder.BASE_DO_FIELDS);
        globalBindingMap.put("QueryWrapperClassName", LambdaQueryWrapper.class.getName());
        globalBindingMap.put("BaseMapperClassName", MPJBaseMapper.class.getName());
        // Util 工具类
        globalBindingMap.put("ServiceExceptionUtilClassName", ServiceExceptionUtil.class.getName());
        globalBindingMap.put("BeanUtils", BeanUtils.class.getName());


    }
}
